package com.org.oracle.service.impl;

import com.baomidou.mybatisplus.core.toolkit.StringUtils;
import com.google.common.base.Preconditions;
import com.google.common.collect.Lists;
import com.mongodb.lang.Nullable;
import com.org.oracle.constant.DayLogConstants;
import com.org.oracle.constant.RedisKeyConstants;
import com.org.oracle.constant.TaskConstants;
import com.org.oracle.constant.TaskListConstants;
import com.org.oracle.enums.ResponseCodeEnum;
import com.org.oracle.sub.entity.TaskList;
import com.org.oracle.mysql.entity.Task;
import com.org.oracle.service.DayLogService;
import com.org.oracle.service.TaskListService;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.dao.DataAccessException;
import org.springframework.data.redis.core.*;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Service;
import org.springframework.transaction.support.TransactionTemplate;

import java.util.*;

/**
 * @author heiye
 * @version 1.0
 * @data 2024/8/19 下午10:36
 */
@Slf4j
@Service
public class TaskListServiceImpl implements TaskListService {

    @Autowired
    private RedisTemplate redisTemplate;

    @Autowired
    private DayLogService dayLogService;

    @Resource
    private TransactionTemplate transactionTemplate;

    // 定时任务需要间隔的用户个数
    private static final long SPACER = 50000;

    /**
     * 添加任务到当日的表单中
     *
     * @param uid
     * @param task
     */
    @Override
    public void taskAdd(String uid, Task task) {
        // 检查参数
        Preconditions.checkArgument(StringUtils.isNotBlank(uid) && Objects.nonNull(task), ResponseCodeEnum.PARAM_REQUIRE);

        // 查找该用户的任务列表是否存在
        TaskList taskList = (TaskList) redisTemplate.opsForValue().get(RedisKeyConstants.TASK_LIST + uid);

        // 用户的任务列表不存在
        if (taskList == null) {
            // 封装任务列表
            taskList = TaskList.builder()
                    .uid(uid)
                    .circleId(task.getCircleId())
                    .list(List.of(task))
                    .over(false)
                    .build();

            // 创建 take_list_uid 的键值
            redisTemplate.opsForValue().set(RedisKeyConstants.TASK_LIST + uid, taskList);
            return;
        }

        // 用户完成今天任务后却又添加了新的任务
        if (taskList.getOver()) {
            taskList.setOver(TaskListConstants.TASK_FAIL);
        }

        // 更新用户任务列表
        taskList.getList().add(task);

        // 重新赋值
        redisTemplate.opsForValue().set(RedisKeyConstants.TASK_LIST + uid, taskList);
    }

    /**
     * 查找用户任务列表
     *
     * @param uid
     * @return
     */
    @Override
    public TaskList findByUid(String uid) {
        // 检查参数
        Preconditions.checkArgument(StringUtils.isNotBlank(uid), ResponseCodeEnum.PARAM_REQUIRE);

        return (TaskList) redisTemplate.opsForValue().get(RedisKeyConstants.TASK_LIST + uid);
    }

    /**
     * 将用户每日任务状态修改为完成
     *
     * @param uid
     * @param task 需要修改的任务
     */
    @Override
    public void taskFinish(String uid, Task task) {
        // 检查参数
        Preconditions.checkArgument(StringUtils.isNotBlank(uid) && Objects.nonNull(task), ResponseCodeEnum.PARAM_REQUIRE);

        // 查询对应用户
        TaskList taskList = (TaskList) redisTemplate.opsForValue().get(RedisKeyConstants.TASK_LIST + uid);

        // 校验数据
        Preconditions.checkArgument(Objects.nonNull(taskList), ResponseCodeEnum.DATA_NOT_EXIST);

        // 更新里面数据
        List<Task> list = taskList.getList();
        for (int i = 0; i < list.size(); i++) {
            // 找到任务名相同的数据后对里面的内容进行修改
            Task item = list.get(i);
            if (item.getName().equals(task.getName())) {
                list.set(i, task);
                break;
            }
        }

        // 判断用户当前的任务是否全部完成
        taskList.setOver(isOver(list));
        // 放回任务列表中
        taskList.setList(list);

        redisTemplate.opsForValue().set(RedisKeyConstants.TASK_LIST + uid, taskList);

    }

    /**
     * 判断用户的每日任务是否全部完成
     *
     * @param list
     * @return
     */
    public Boolean isOver(List<Task> list) {
        for (Task task : list) {
            if (task.getState().equals(TaskConstants.CHALLENGED)) {
                return TaskListConstants.TASK_FAIL;
            }
        }
        return TaskListConstants.TASK_ALL_FINISH;
    }

    /**
     * 每日凌晨4点对用户的每日任务进行扫描
     */
    @Scheduled(cron = "0 0 4 * * ?")
    public void taskListClear() {
        transactionTemplate.execute(status -> {
            try {
                log.info("刷新用户任务列表...");

                // 在此处维护循环边界
                long start = 1000001;
                long end = start + SPACER;

                // 获取所有键值的数据
                List<TaskList> taskLists = redisGetPipelining(start, end);

                // 由于使用了 redis 管道技术 尽管在管道里返回了 null
                // 但 taskLists 里无论如何都会存在一个为 null 的对象
                // 也就是说 taskLists 的 size 始终为 1
                // 这也意味着 CollUtil.isEmpty() 无法判断 taskLists
                // 所以通过 taskLists 里第一个元素来进行判断
                while (!Objects.isNull(taskLists.get(0))) {
                    // 过滤不符合需求的数据
                    List<TaskList> newTaskLists = taskListFilter(taskLists);

                    // 使用redis管道技术批量处理数据
                    redisSetPipelining(newTaskLists);

                    // 重新定义边界
                    start = end;
                    end = start + SPACER;

                    // 重新获取到用户的任务列表
                    taskLists = redisGetPipelining(start, end);
                }

                log.info("刷新用户任务列表完成");
                return null;
            } catch (Exception e) {
                // 回归事务
                status.setRollbackOnly();
                log.error("===> 刷新用户任务列表失败", e);
                return null;
            }
        });
    }

    /**
     * redis添加管道
     *
     * @param taskLists 批处理数据
     */
    public void redisSetPipelining(List<TaskList> taskLists) {
        redisTemplate.executePipelined(new SessionCallback<>() {
            @Nullable
            @Override
            public Object execute(RedisOperations redisOperations) throws DataAccessException {
                for (TaskList taskList : taskLists) {
                    redisOperations.opsForValue().set((RedisKeyConstants.TASK_LIST + taskList.getUid()), taskList);
                }
                return null;
            }
        });
    }

    /**
     * redis获取管道
     *
     * @param start  最开始用户id
     * @param end 循环结束用户id
     * @return
     */
    public List<TaskList> redisGetPipelining(long start, long end) {
        return redisTemplate.executePipelined(new SessionCallback<>() {
            @Nullable
            @Override
            public Object execute(RedisOperations redisOperations) throws DataAccessException {
                List<TaskList> taskLists = Lists.newArrayList();
                for (long i = start; i < end; i++) {
                    TaskList taskList = (TaskList) redisOperations.opsForValue().get(RedisKeyConstants.TASK_LIST + i);
                    // 如果获取不到数据了 直接退出
                    if (Objects.isNull(taskList)) {
                        return null;
                    }
                    // 添加到列表中
                    taskLists.add(taskList);
                }
                return taskLists;
            }
        });
    }

    /**
     * 过滤不符合需求的数据 同时 记录打卡
     *
     * @param taskLists
     * @return
     */
    public List<TaskList> taskListFilter(List<TaskList> taskLists) {
        // 创建一个列表过滤不符合需求的数据后赋值
        List<TaskList> newTaskLists = Lists.newArrayList();
        for (TaskList taskList : taskLists) {
            // 取得里面的List<Task>
            List<Task> list = taskList.getList();
            // 用户已经将任务全部完成
            if (taskList.getOver()) {
                // 每日打卡
                dayLogService.log(taskList, DayLogConstants.OVER);

                // 清空list的数据
                taskList.setList(Lists.newArrayList());
                taskList.setOver(TaskListConstants.TASK_FAIL);

                // 放入newTaskLists中
                newTaskLists.add(taskList);
                continue;
            }

            // 将任务没有全部完成的用户进行打卡
            dayLogService.log(taskList, DayLogConstants.NOT_OVER);

            // 排除掉今日没有接任务的用户
            if (list.isEmpty()) {
                continue;
            }

            // 创建一个新的List<Task>
            List<Task> newList = Lists.newArrayList();
            for (Task task : list) {
                // 如果任务状态是挑战中则赋值到新数组中
                if (task.getState().equals(TaskConstants.CHALLENGED)) {
                    newList.add(task);
                }
            }

            // 对taskList重新赋值
            taskList.setList(newList);
            taskList.setOver(TaskListConstants.TASK_FAIL);

            // 放入newTaskLists中
            newTaskLists.add(taskList);
        }

        return newTaskLists;
    }
}
